﻿/********************************************************************************/
/* 	character.d																	*/
/*------------------------------------------------------------------------------*/
/*	製作		（　゜ワ゜）ノ　／　松久貫太									*/
/*	製作開始　	2008/01/03														*/
/*	MAIL		omega@personal.email.ne.jp										*/
/*	URL		http://nagoya.cool.ne.jp/o_mega										*/
/*																				*/
/*	このソースは「やわらかライセンス」の元で配布されています。					*/
/*-更新履歴---------------------------------------------------------------------*/
/*	2008/01/04																	*/
/*-その他-----------------------------------------------------------------------*/
/*	なし																		*/
/********************************************************************************/

private import gamecore;
private import hell2;
private import boot;
private import actor;
private import vector;
private import keypad;
private import map;

//********************************************************************************
// キャラクタ
//********************************************************************************

class Character : Actor{
	enum TYPE{
		NONE = 0,
		SHADOW = 0,
		NICOCHU,
		VC_MK,
		RAINBOW_RAY,
		EXPLODE,
		UP_NUSHI,
		MAX,
	}
	// 名称
	static string type_name[] =
		["SHADOW" , "NICOCHU" , "SOWAKA" , "RAINBOW_RAY" , "EXPLODE" , "UP_NUSHI" , "MAX"];
	static const double type_movespeed[] =
		[   0.0 ,  0.03 ,   0.1 ,   0.5 ,     0 ,  0.03 ];		// 移動速度
	static const bool type_shadow[] =
		[ false ,  true ,  true , false , false ,  true ];		// 影の有無
	static const bool type_shake[] =
		[ false ,  true ,  true , false , false ,  true ];		// 揺れの有無
	static const int type_life_max[] =
		[     0 ,    20 ,  9999 ,     0 ,     0 ,    20 ];		// ライフ上限値
	static const bool type_blocking[] =	
		[ false ,  true ,  true , false , false ,  true ];		// 衝突判定の有無
	static const int type_life_dec[] =
		[	  0 ,    40 ,     0 ,     0 ,     0 ,    40 ];		// ライフ減少時間(フレーム)
	
	// 方向
	enum DIRECTION{
		DOWN = 0,
		RIGHT,
		LEFT,
		UP,
		MAX,
	}
	double vecx[] = [  0 ,  1 , -1 ,  0 ];	// ベクトル
	double vecy[] = [  1 ,  0 ,  0 , -1 ];
	
	// 行動
	enum MOTION{
		NORMAL = 0,		// 通常
		WALK,			// 移動
		DAMAGED,		// 非ダメージ
		SHOOTING,		// 射撃中(ソワカちゃん向け)
		VANISH,			// 消失(爆発エフェクト向け)
		DYING,			// 死亡中
		EATING,			// もぐもぐ中
		SPAWNING,		// 増殖中
	}
	static const int motion_interval[] = [ 0 , 999 , 20 , 2 , 24 , 40 , 20 , 40];
	
	DIRECTION direction;	// 方向
	MOTION motion;			// モーション
	int motion_wait;		// モーション待ち時間(フレーム)
	double move_fraction;	// 移動時の端数(0～1)
	TYPE type;
	int timer;
	Keypad keypad;
	
	int life;
	
	GameMain gamemain;
	
	this(){
		position = new Vec3();
		type = TYPE.NICOCHU;
		move_fraction = 0;
		timer = 0;
		direction = DIRECTION.DOWN;
		motion = MOTION.NORMAL;
		life = 0;
	}
	
	void init(GameMain gamemain , Vec3 pos , TYPE type){
		this.gamemain = gamemain;
		this.position = pos;
		this.type = type;
		this.exist = true;
		this.motion = MOTION.NORMAL;
		this.move_fraction = 0;
		this.keypad = null;
		this.timer = 0;
		
		if(type == TYPE.VC_MK) this.keypad = new Keypad();
		this.life = type_life_max[type];
	}
	
	Vec3 getPosition(){return position;}
	Vec3 getDestination(){
		if(motion != MOTION.WALK) return position;
		return new Vec3(position.x + vecx[direction] , position.y + vecy[direction] , 0);
	}
	
	void move(){
		timer++;
		
		Map map = gamemain.map;
		int x = cast(int)position.x , y = cast(int)position.y;
		
		// 画面外
		if(x < 0 || y < 0 || x >= map.width || y >= map.height){
			exist = false;
		}
		
		// ライフ減少
		if(type_life_dec[type] > 0 && timer % type_life_dec[type] == 0){
			life--;
		}
		
		// モーション管理
		motion_wait--;
		if(motion_wait < 1){
			motion_wait = 0;
			if(motion == MOTION.VANISH || motion == MOTION.DYING) exist = false;
			if(motion == MOTION.DYING){
				if(map.getGrassLevel(x,y) < 2) map.setGrass(x,y,20);
			}
			motion = MOTION.NORMAL;
		}
		
		// 死亡モーション遷移
		if(life < 0  && motion != MOTION.VANISH && motion != MOTION.DYING && type_life_max[type] > 0){
			setMotion(MOTION.DYING);
			life = 0;
		}
		if(motion == MOTION.DYING) return;
		
		// キャラクタ固有行動
		switch(type){
		
			// ニコ厨
			case TYPE.NICOCHU:
				if(motion == MOTION.NORMAL && Hell_randInt(0,10) < 5){
					if(eatGrass()){
						setMotion(MOTION.EATING);
						selectNewDirection();
					}
				}
				if(motion == MOTION.EATING) map.setGrass(x,y,0);
				if(motion == MOTION.NORMAL){
					int t = Hell_randInt(0,100);
					if(t < configparser.getint("NICOCHU_TURN_RATIO")){
						int gl = cast(int)map.getGrassLevel(x-1,y);
						int gr = cast(int)map.getGrassLevel(x+1,y);
						int gu = cast(int)map.getGrassLevel(x,y-1);
						int gd = cast(int)map.getGrassLevel(x,y+1);
						
						if(gl+gr+gu+gd == 0){
							selectNewDirection();
						}else{
							int r = Hell_randInt(0,gl+gr+gu+gd);
							if(r < gl){
								direction = DIRECTION.LEFT;
							}else if(r < gl + gr){
								direction = DIRECTION.RIGHT;
							}else if(r < gl + gr + gu){
								direction = DIRECTION.UP;
							}else{
								direction = DIRECTION.DOWN;
							}
						}
					}
					setMotion(MOTION.WALK);
					
				}
				
				if(timer % (configparser.getint("UPNUSHI_TRANSFORM_INTERVAL") * 40) == 0
					&& Hell_randInt(0,100) < configparser.getint("UPNUSHI_TRANSFORM_RATIO") )
				{
					exist = false;
					
					Character c = gamemain.charpool.set( new Vec3(position) , TYPE.UP_NUSHI , direction );
				}
				break;
			
			// ソワカちゃん
			case TYPE.VC_MK:
				controlCharacter();
				life = type_life_max[type];	// 常時初期ライフ
				break;
				
			// びーむ
			case TYPE.RAINBOW_RAY:
				if(motion == MOTION.NORMAL) setMotion(MOTION.WALK);
				destroyNeigbor();
				break;
			
			// 爆風
			case TYPE.EXPLODE:
				break;
			
			// うp主
			case TYPE.UP_NUSHI:
				if(motion == MOTION.NORMAL){
					if(Hell_randInt(0,10) < 3){
						selectNewDirection();
					}
					setMotion(MOTION.WALK);
				}
				
				if(life < 1){
					map.set(cast(int)position.x , cast(int)position.y , MapChip.TYPE.MOVIE);
					setMotion(MOTION.DYING);
				}
				
				break;
			
			default:
				break;
		}
		actCharacter();
	}
	
	// 移動とか汎用処理
	void actCharacter(){
		if(motion == MOTION.WALK){
			// 移動
			move_fraction += type_movespeed[cast(int)type];
			if(move_fraction >= 1.0){
				move_fraction = 1.0;
				position.x = cast(int)toMapXf();
				position.y = cast(int)toMapYf();
				
				move_fraction = 0;
				setMotion(MOTION.NORMAL);
			}
			
			// 移動先の確認
			int x = cast(int)(position.x + vecx[cast(int)direction]);
			int y = cast(int)(position.y + vecy[cast(int)direction]);
			if( !gamemain.map.isMovable(x,y) && isBlocking() ){
				move_fraction = 0;
				setMotion(MOTION.NORMAL);
			}
		}
	}
	
	bool isBlocking(){
		return type_blocking[type];
	}
	
	// 移動方向をランダム決定する(障害物のない方向を向く)
	void selectNewDirection(){
		bool chk;
		int t = 0;
		do{
			t++;
			direction = cast(DIRECTION) Hell_randInt(0,DIRECTION.MAX);
			int x = cast(int)(position.x + vecx[direction]);
			int y = cast(int)(position.y + vecy[direction]);
			chk = gamemain.map.isMovable(x,y);
		}while(!chk && t < 32);
	}
	
	// ソワカちゃん専用処理(プレーヤー操作)
	void controlCharacter(){
		// 七色レーザー発射
		if(keypad.isPressEnter() && motion != MOTION.SHOOTING){
			Character c = gamemain.charpool.set( new Vec3(position) , TYPE.RAINBOW_RAY , direction );
			if(c){
				c.move_fraction = move_fraction;
				setMotion(MOTION.SHOOTING);
			}
		}
		
		// パッド操作
		if(motion == MOTION.NORMAL){
			if(move_fraction != 0){
				setMotion(MOTION.WALK);
				return;
			}
			
			// 移動方向決定
			if(keypad.isPressDown()){
				direction = DIRECTION.DOWN;
				setMotion(MOTION.WALK);
			}
			
			if(keypad.isPressRight()){
				direction = DIRECTION.RIGHT;
				setMotion(MOTION.WALK);
			}
			
			if(keypad.isPressUp()){
				direction = DIRECTION.UP;
				setMotion(MOTION.WALK);
			}
			
			if(keypad.isPressLeft()){
				direction = DIRECTION.LEFT;
				setMotion(MOTION.WALK);
			}
		}
	}
	
	// 芝を食うの図
	bool eatGrass(){
		int x = cast(int)position.x , y = cast(int)position.y;
		
		if(move_fraction != 0) return false;
		if(gamemain.map.getGrassLevel(x,y) < 1) return false;	// レベル2以上の芝しか食べない
		
		// 芝を食う
		life += gamemain.map.getGrassLevel(x,y) * 2;
		if(life >= type_life_max[type]) {
			// ライフ上限を超えたらライフを半分にして増殖
			life /= 2;
			
			Character c = gamemain.charpool.set( new Vec3(position.x , position.y , 0) , TYPE.NICOCHU);
			if(c){
				c.life = life;
				while(c.direction == direction){
					c.direction = cast(DIRECTION) Hell_randInt( 0 , DIRECTION.MAX );
				}
			}
		}
		gamemain.map.setGrass(x,y,0);
		return true;
	}
	
	// ぶっとばすぞー
	void destroyNeigbor(){
		for(int x=-1;x<2;x++){
			for(int y=-1;y<2;y++){
				int px = cast(int)position.x + x;
				int py = cast(int)position.y + y;
				
				if(!gamemain.map.isMap( px , py , MapChip.TYPE.NONE)){
					Character c = gamemain.charpool.set( new Vec3(px , py , 0) , TYPE.EXPLODE);
					if(c){
						c.setMotion(MOTION.VANISH);
					}
				}
				gamemain.map.set( px , py , MapChip.TYPE.NONE);
				
				Character ch[] = gamemain.charpool.getCharacterAtMap(px,py);
				
				foreach(inout c;ch){
					// VC_MKでなく、ライフがあるキャラば爆発しろ!
					if(c && c.exist && c.type != TYPE.VC_MK && type_life_max[c.type] > 0){
						c.destroy();
					}
				}
			}
		}
	}
	
	// 爆発しろ
	void destroy(){
		exist = false;
		
		Character c = gamemain.charpool.set( new Vec3(position) , TYPE.EXPLODE , direction );
		if(c){
			c.move_fraction = move_fraction;
			c.setMotion(MOTION.VANISH);
		}
	}
	
	// モーションセット
	void setMotion(MOTION m){
		this.motion = m;
		this.motion_wait = motion_interval[m];
	}
	
	// 作画
	void draw(DRAW_PRIORITY dp = DRAW_PRIORITY.NORMAL){
		if(dp == DRAW_PRIORITY.NORMAL){
			Map map = gamemain.map;
			double mx = toMapXf() , my = toMapYf();
			// アルファブレンド
			int alpha = 255;
			if(motion == MOTION.DYING) alpha = 255 * motion_wait / motion_interval[motion]; 
			
			// 影
			if(type_shadow[cast(int)type]){
				/* 速度を稼ぐため、下半分だけコピーする */
				Hell_drawTexture("char" , map.toViewX(mx,my) , map.toViewY(mx,my) + CHIP_SIZE / 2
					,CHIP_SIZE * 0 + CHIP_SIZE * 5
					,CHIP_SIZE * TYPE.SHADOW + CHIP_SIZE / 2
					,CHIP_SIZE , CHIP_SIZE / 2 , 1 , 1
					,0,255,255,255,alpha);
				/* 本来のコード
				Hell_drawTexture("char" , map.toViewX(mx,my) , map.toViewY(mx,my)
					,CHIP_SIZE * 0 + CHIP_SIZE * 5
					,CHIP_SIZE * TYPE.SHADOW
					,CHIP_SIZE , CHIP_SIZE , 1 , 1
					,0,255,255,255,alpha);
				*/
			}
			
			int shake = type_shake[cast(int)type] ? ((timer / 8) % 2) : 0 ;
			int reverse = (direction == DIRECTION.RIGHT || direction == DIRECTION.UP) ? -1 : 1;
			if(motion == MOTION.NORMAL || motion == MOTION.WALK){
				// 通常
				Hell_drawTexture("char"
					, map.toViewX(mx,my) + (1 - reverse) * CHIP_SIZE / 2 , map.toViewY(mx,my) + shake
					,CHIP_SIZE * (cast(int)direction / 2) + CHIP_SIZE * 5
					,CHIP_SIZE * type
					,CHIP_SIZE , CHIP_SIZE , reverse , 1.0);
			}else if(motion == MOTION.DAMAGED){
				// ダメージ
				if(timer % 4 < 2)
				Hell_drawTexture("char"
					, map.toViewX(mx,my) + (1 - reverse) * CHIP_SIZE / 2 , map.toViewY(mx,my)
					,CHIP_SIZE * (cast(int)direction / 2) + CHIP_SIZE * 5
					,CHIP_SIZE * type
					,CHIP_SIZE , CHIP_SIZE , reverse , 1.0);
			}else if(motion == MOTION.SHOOTING){
				// 射撃
				Hell_drawTexture("char"
					, map.toViewX(mx,my) + (1 - reverse) * CHIP_SIZE / 2 , map.toViewY(mx,my)
					,CHIP_SIZE * (cast(int)direction / 2 + 2) + CHIP_SIZE * 5
					,CHIP_SIZE * type
					,CHIP_SIZE , CHIP_SIZE , reverse , 1.0);
			}else if(motion == MOTION.VANISH){
				// 消失
				Hell_drawTexture("char"
					, map.toViewX(mx,my) , map.toViewY(mx,my)
					,CHIP_SIZE * (timer / 6) + CHIP_SIZE * 5
					,CHIP_SIZE * type
					,CHIP_SIZE , CHIP_SIZE);
			}else if(motion == MOTION.DYING){
				// 死亡
				int fly_y = (motion_interval[MOTION.DYING] - motion_wait);
				Hell_drawTexture("char"
					, map.toViewX(mx,my) , map.toViewY(mx,my) - CHIP_SIZE / 4 - fly_y
					,CHIP_SIZE * 6
					,CHIP_SIZE * 0
					,CHIP_SIZE , CHIP_SIZE , 1 , 1
					,0,255,255,255,alpha);
				Hell_drawTexture("char"
					, map.toViewX(mx,my) + (1 - reverse) * CHIP_SIZE / 2 , map.toViewY(mx,my) - fly_y
					,CHIP_SIZE * (cast(int)direction / 2) + CHIP_SIZE * 5
					,CHIP_SIZE * type
					,CHIP_SIZE , CHIP_SIZE , reverse , 1
					,0,255,255,255,alpha);
			}else if(motion == MOTION.EATING){
				// 食事中
				Hell_drawTexture("char"
					, map.toViewX(mx,my) + (1 - reverse) * CHIP_SIZE / 2 , map.toViewY(mx,my) - ((timer / 3) % 2) * 2
					,CHIP_SIZE * (cast(int)direction / 2) + CHIP_SIZE * 5
					,CHIP_SIZE * type
					,CHIP_SIZE , CHIP_SIZE , reverse , 1.0);
			}
		}else{
		}
	}
	
	// ステータス
	void drawStatus(){
		Map map = gamemain.map;
		double mx = toMapXf() , my = toMapYf();
		int vx = cast(int)map.toViewX(mx,my);
		int vy = cast(int)map.toViewY(mx,my);
		
		Hell_drawRect(vx - 8 * 11  , vy - 16 , 8 * 12 , 8 * 6 , 0,0, 255,255,255,192);
		Hell_drawFont(type_name[cast(int)type] , vx - 8 * 10  , vy - 8);
		Hell_drawFont("LIFE:" ~ std.string.toString(life) , vx - 8 * 10 , vy + 2);
		Hell_drawFont("TIME:" ~ std.string.toString(timer / 40) , vx - 8 * 10 , vy + 10);
		//Hell_drawFont("MOT:" ~ std.string.toString(cast(int)motion) , vx - 8 * 10 , vy + 10);
		//Hell_drawFont("MOW:" ~ std.string.toString(cast(int)motion_wait) , vx - 8 * 10 , vy + 18);
	}
	
	double toMapXf(){
		return position.x + move_fraction * vecx[cast(int)direction];
	}
	double toMapYf(){
		return position.y + move_fraction * vecy[cast(int)direction];
	}
}


//********************************************************************************
// キャラクタプール
//********************************************************************************

class CharacterPool : ActorPool ! (Character){
	GameMain gamemain;
	int width;
	int height;
	
	// キャラクタのバケットソート用クラス
	class CharacterBacket{
		const static int BACKET_MAX = 4;
		Character char_ptr[];
		
		this(){
			char_ptr = new Character[BACKET_MAX];
			init();
		}
		
		void init(){
			for(int t=0;t<BACKET_MAX;t++) char_ptr[t] = null;
		}
		
		void entry(inout Character ch){
			for(int t=0;t<BACKET_MAX;t++){
				if(char_ptr[t] is null){
					char_ptr[t] = ch;
					return;
				}
			}
		}
		
		// バケット内のキャラを描く
		void draw(){
			for(int t=0;t<BACKET_MAX;t++){
				if(char_ptr[t]) char_ptr[t].draw();
			}
		}
	}
	CharacterBacket charbacket[];
	
	this(GameMain gamemain , int n , int width , int height){
		this.gamemain = gamemain;
		
		this.width = width;
		this.height = height;
		this.charbacket = new CharacterBacket[width * height];
		foreach(inout c;this.charbacket) c = new CharacterBacket();
		
		super(n);
	}
	
	// キャラクタ出現
	Character set(Vec3 pos , Character.TYPE type , Character.DIRECTION direction = Character.DIRECTION.DOWN){
		Character c = getInstance();
		if(c){
			c.init(gamemain , pos , type);
			c.direction = direction;
			return c;
		}else{
			return null;
		}
	}
	
	// キャラクタ数を数える
	int count(Character.TYPE type = Character.TYPE.NONE){
		int result = 0;
		foreach(c;actors){
			if(c.exist == true &&(type == Character.TYPE.NONE || c.type == type)) result++;
		}
		return result;
	}
	
	void move(){
		// 動かす
		super.move();
		updateBacket();
	}
	
	// バケットソートを更新する
	void updateBacket(){
		// バケット初期化
		foreach(inout c;charbacket) c.init();
		
		// バケットソート
		foreach(inout c;actors){
			if(c.exist){
				int x = cast(int)(c.toMapXf());
				int y = cast(int)(c.toMapYf());
				if(!inRange(x,y)) continue;
				
				charbacket[x + y * width].entry(c);
			}
		}
	}
	
	// ステータス作画(フラグが立っているもののみ)
	void drawStatus(){
		foreach(inout c;actors){
			if(c.exist) c.drawStatus();
		}
	}
	
	// 作画(with バケットソート)
	void drawAtMap(int x,int y){
		if(!inRange(x,y)) return;
		charbacket[x + y * width].draw();
	}
	
	// キャラ取得(with バケットソート)
	Character[] getCharacterAtMap(int x,int y){
		if(!inRange(x,y)) return null;
		return charbacket[x + y * width].char_ptr;
	}
	
	// マップ端からランダム進入
	void entryFromMapBorder(Character.TYPE type){
		int x,y;
		int rndkey = Hell_randInt(0 , (width + height));
		
		Character c = set( new Vec3() , type);
		if(!c) return;
		
		if(rndkey > width){
			// y軸方面から進入
			c.position.y = rndkey - width;
			if(Hell_randInt(0,10) < 5){
				// 左から
				c.position.x = 0;
				c.direction = Character.DIRECTION.RIGHT;
			}else{
				// 右から
				c.position.x = width - 1;
				c.direction = Character.DIRECTION.LEFT;
			}
		}else{
			// x軸方向から進入
			c.position.x = rndkey;
			if(Hell_randInt(0,10) < 5){
				// 上から
				c.position.y = 0;
				c.direction = Character.DIRECTION.DOWN;
			}else{
				// 下から
				c.position.y = height - 1;
				c.direction = Character.DIRECTION.UP;
			}
		}
	}
	
	// キャラクタを取り除く
	void removeCharacter(Character.TYPE type){
		foreach(inout c;actors){
			if(c.exist && c.type == type) c.exist = false;
		}
	}
	
	bool inRange(int x,int y){
		if(x < 0 || y < 0 || x >= width || y >= height) return false;
		return true;
	}
}
